#include "config.h"
#include "configure.h"

#include <errno.h>

#define DBG_SUBSYS S_LIBYNET

#include "ynet_net.h"
#include "ynet_rpc.h"
#include "net_global.h"
#include "regex.h"
#include "fence.h"
#include "fnotify.h"
#include "dbg.h"

static hashtable_t table = NULL;
static sy_spinlock_t table_lock;
static char __path__[MAX_PATH_LEN];

typedef struct {
        char *addr;
        char *site;
        char *rack;
        char *node;
} entry_t;

static int __hosts_match(const char *buf, const char *regstr, char *res1, char *res2)
{
        int ret;
        regex_t reg;
        regmatch_t match;
        char errorbuf[MAX_BUF_LEN];
        const char *ptr;

        ret = regcomp(&reg, regstr, 0);
        if (unlikely(ret)) {
                regerror(ret, &reg, errorbuf, MAX_BUF_LEN);
                DERROR("%s %d\n", errorbuf, ret);
                GOTO(err_ret, ret);
        }

        ret = regexec(&reg, buf, 1, &match, 0);
        if (unlikely(ret)) {
                if (ret == REG_NOMATCH) {
                        ret = ENOENT;
                        DINFO("key %s buf %s\n", regstr, buf);
                        GOTO(err_free,ret);
                } else {
                        regerror(ret, &reg, errorbuf, MAX_BUF_LEN);
                        DERROR("%s %d\n", errorbuf, ret);
                        GOTO(err_free, ret);
                }
        }

        memcpy(res1, &buf[match.rm_so], match.rm_eo - match.rm_so);
        res1[match.rm_eo - match.rm_so] = '\0';

        if (res2) {
                ptr = &buf[match.rm_eo];

                ret = regexec(&reg, ptr, 1, &match, 0);
                if (unlikely(ret)) {
                        if (ret == REG_NOMATCH) {
                                ret = ENOENT;
                                DINFO("key %s buf %s\n", regstr, buf);
                                GOTO(err_free, ret);
                        } else {
                                regerror(ret, &reg, errorbuf, MAX_BUF_LEN);
                                DERROR("%s %d\n", errorbuf, ret);
                                GOTO(err_free, ret);
                        }
                }

                memcpy(res2, &ptr[match.rm_so], match.rm_eo - match.rm_so);
                res2[match.rm_eo - match.rm_so] = '\0';
        }

        regfree(&reg);

        return 0;
err_free:
        regfree(&reg);
err_ret:
        return ret;
}

static int __hosts_splitline(const char *buf, char *addr, char *host)
{
        int ret, count;
        char _addr[MAX_BUF_LEN], _host[MAX_BUF_LEN], *ptr;

        ret = __hosts_match(buf, "[^\t ]\\+", _addr, _host);
        if (unlikely(ret)) {
                DWARN("(%s)\n", buf);
                GOTO(err_ret, ret);
        }

        DBUG("line (%s) addr (%s) host (%s)\n", buf, _addr, _host);

        ret = __hosts_match(_addr, "^[0-9]\\+.[0-9]\\+.[0-9]\\+.[0-9]\\+", addr, NULL);
        if (unlikely(ret)) {
                ret = EINVAL;
                DINFO("skip %s %s\n", buf, _addr);
                GOTO(err_ret, ret);
        }

        count = 0;
        ptr = _host;
        while (1) {
                ptr = strchr(ptr, '.');
                DBUG("str %s\n", ptr);
                if (ptr == NULL)
                        break;
                ptr++;
                count++;
        }

        if (count > 2) {
                ret = EINVAL;
                DBUG("skip %s\n", buf);
                GOTO(err_ret, ret);
        }

#if 0
        ret = __hosts_match(_host, "[a-z0-9A-Z]\\+.[a-z0-9A-Z]\\+.[a-z0-9A-Z]\\+.[a-z0-9A-Z]\\+$", host, NULL);
        if (ret == 0) {
                ret = EINVAL;
                DWARN("skip %s %s\n", buf, _host);
                GOTO(err_ret, ret);
        }
#endif

        strcpy(host, _host);

        return 0;
err_ret:
        return ret;
}

static int __hosts_insert(const char *buf)
{
        int ret;
        entry_t *ent;
        char site[MAX_NAME_LEN], rack[MAX_NAME_LEN], node[MAX_NAME_LEN];
        char addr[MAX_NAME_LEN], host[MAX_NAME_LEN], tmp[MAX_NAME_LEN], disk[MAX_NAME_LEN];

        ret = __hosts_splitline(buf, addr, host);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        snprintf(tmp, sizeof(tmp), "%s/0", host);
        hosts_split(tmp, site, rack, node, disk);

        ret = ymalloc((void **)&ent, sizeof(*ent));
        if (unlikely(ret))
                GOTO(err_ret, ret);

        memset(ent, 0x0, sizeof(*ent));

        ret = ymalloc((void **)&ent->addr, strlen(addr) + 1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = ymalloc((void **)&ent->site, strlen(site) + 1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = ymalloc((void **)&ent->rack, strlen(rack) + 1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = ymalloc((void **)&ent->node, strlen(node) + 1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        strcpy(ent->addr, addr);
        strcpy(ent->site, site);
        strcpy(ent->rack, rack);
        strcpy(ent->node, node);

        DINFO("host:%s site:%s rack:%s node:%s\n", addr, site, rack, node);

        ret = hash_table_insert(table, (void *)ent, ent->addr, 0);
        if (unlikely(ret)) {
                GOTO(err_ret, ret);
        }

        return 0;
err_ret:
        return ret;
}

static int __hosts_load(int *_count)
{
        int ret, count, len;
        char hostline[STR_LEN], path[MAX_PATH_LEN];
        FILE *fp;

        count = 0;

        fp = fopen(__path__, "r");
        if (fp == NULL) {
                ret = errno;
                if (ret == ENOENT) {
                        goto out;
                } else {
                        DWARN("fence arping open config file %s failed\n", path);
                        GOTO(err_ret, ret);
                }
        }

        while (1) {
                if (fgets(hostline, STR_LEN, fp) == NULL) {
                        break;
                }

                if (hostline[0] == '#')
                        continue;

                len = strlen(hostline);
                if (hostline[len - 1] == '\n')
                        hostline[len - 1] = '\0';

                if (strlen(hostline) == 0)
                        continue;

                ret = __hosts_insert(hostline);
                if (unlikely(ret)) {
                        continue;
                }
        }

        fclose(fp);
out:
        *_count = count;

        return 0;
err_ret:
        return ret;
}

static uint32_t __key(const void *args)
{
        return hash_str((char *)args);
}

static int __cmp(const void *v1, const void *v2)
{
        const entry_t *ent = (entry_t *)v1;

        return strcmp(ent->addr, (const char *)v2);
}

static void __hosts_free(void *_ent)
{
        entry_t *ent = _ent;

        if (ent->addr)
                yfree((void **)&ent->addr);

        if (ent->site)
                yfree((void **)&ent->site);

        if (ent->rack)
                yfree((void **)&ent->rack);

        if (ent->node)
                yfree((void **)&ent->node);
}

static void __hosts_destroy()
{
        hash_destroy_table(table, __hosts_free);
}


static int __hosts_fnotify(void *context, uint32_t mask)
{
        int ret, count;

        (void) context;
        (void) mask;

        DINFO("hosts.conf table reload\n");

        ret = sy_spin_lock(&table_lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        __hosts_destroy();

        table = hash_create_table(__cmp, __key, "hosts table");
        if (table == NULL) {
                ret = ENOMEM;
                GOTO(err_lock, ret);
        }

        ret = __hosts_load(&count);
        if (unlikely(ret))
                GOTO(err_lock, ret);

        sy_spin_unlock(&table_lock);

        return 0;
err_lock:
        sy_spin_unlock(&table_lock);
err_ret:
        return ret;
}

static int __hosts_fnotify_del(void *context, uint32_t mask)
{
        int ret;

        (void) context;
        (void)mask;

        DWARN("file removed, %s\n", __path__);

        sleep(3);

        if (fopen(__path__, "r") == NULL) {
                DWARN("file not found, %s\n", __path__);
                EXIT(EAGAIN);
        }

        ret = fnotify_register(__path__, __hosts_fnotify, __hosts_fnotify_del, NULL);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        __hosts_fnotify(context, mask);

        return 0;
err_ret:
        return ret;
}

static int __hosts_map(const char *addr, char *site, char *rack, char *node)
{
        int ret;
        entry_t *ent;

        ret = sy_spin_lock(&table_lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ent = hash_table_find(table, (void *)addr);
        if (ent == NULL) {
                strcpy(rack, "default");
                strcpy(site, "default");
                strcpy(node, addr);
                goto out;
        }

        strcpy(site, ent->site);
        strcpy(rack, ent->rack);
        strcpy(node, ent->node);

out:
        sy_spin_unlock(&table_lock);

        return 0;
err_ret:
        return ret;
}

static void __hosts_split_disk(const char *name, char *node, char *disk)
{
        char *c;

        strcpy(node, name);

        c = strchr(node, '/');
        if (c) {
                *c = '\0';
                strcpy(disk, c + 1);
        } else {
                strcpy(disk, "0");
        }
}

static void __hosts_split_dot(const char *name, char *rack, char *node)
{
        char *c;
        int len;

        strcpy(rack, name);

        c = strrchr(name, '.');
        if (c) {
                len = c - name;
                strcpy(node, c + 1);
                memcpy(rack, name, len);
                rack[len] = '\0';
        } else {
                strcpy(node, name);
                strcpy(rack, "default");
        }
}

void __ltrim(char *s)
{
        int l = 0, p = 0, k = 0;

        l = strlen(s);
        if(l == 0) {
                return;
        }

        p = 0;
        while (s[p] == ' ' || s[p] == '\t') {
                p++;
        }

        if( p == 0 ) {
                return;
        }

        while (s[k] != '\0') {
                s[k++] = s[p++];
        }

        return;
}

/**
 *
 * @param _name
 * @param site
 * @param rack
 * @param node
 * @param disk
 */
void hosts_split(const char *_name, char *site, char *rack, char *node, char *disk)
{
        int ret, i;
        char tmp[MAX_NAME_LEN], tmp1[MAX_NAME_LEN], tmp2[MAX_NAME_LEN], tmp3[MAX_NAME_LEN];
        uint32_t d1, d2, d3, d4, d5;
        char pre[] = "default.";
        char *name = tmp2;
        char *ip = tmp3;

        strcpy(name, _name);
        for (i=0; i<2; i++) {
                if (_startswith(name, pre)) {
                        name = name + strlen(pre);
                } else {
                        break;
                }
        }

        ret = sscanf(name, "%d.%d.%d.%d/%d", &d1, &d2, &d3, &d4, &d5);
        if (ret == 5) {
                snprintf(disk, MAX_PATH_LEN, "%d", d5);
                snprintf(ip, MAX_NAME_LEN, "%d.%d.%d.%d", d1, d2, d3, d4);

                __hosts_map(ip, site, rack, node);
                DBUG("name %s\n", name);
        } else {
                ret = sscanf(name, "%d.%d.%d.%d", &d1, &d2, &d3, &d4);
                if (ret == 4) {
                        __hosts_map(name, site, rack, node);
                        snprintf(disk, MAX_PATH_LEN, "%d", 0);
                } else {
                        __hosts_split_disk(name, tmp, disk);
                        __hosts_split_dot(tmp, tmp1, node);
                        __hosts_split_dot(tmp1, site, rack);
                }
        }

        DBUG("name: %s, _site: %s, _rack: %s, _node: %s, _disk: %s\n",
                        name, site, rack, node, disk);
}

void disk2node(const char *disk, char *node)
{
        char _site[MAX_NAME_LEN], _rack[MAX_NAME_LEN],
             _node[MAX_NAME_LEN],  _disk[MAX_NAME_LEN];

        hosts_split(disk, _site, _rack, _node, _disk);
        snprintf(node, MAX_NAME_LEN, "%s.%s.%s", _site, _rack, _node);
}

void disk2rack(const char *disk, char *rack)
{
        char _site[MAX_NAME_LEN],_rack[MAX_NAME_LEN],
             _node[MAX_NAME_LEN], _disk[MAX_NAME_LEN];

        hosts_split(disk, _site, _rack, _node, _disk);
        snprintf(rack, MAX_NAME_LEN, "%s.%s", _site, _rack);
}

void disk2site(const char *disk, char *site)
{
        char _site[MAX_NAME_LEN],_rack[MAX_NAME_LEN],
             _node[MAX_NAME_LEN], _disk[MAX_NAME_LEN];

        hosts_split(disk, _site, _rack, _node, _disk);
        strcpy(site, _site);
}

int hosts_init()
{
        int ret, count;
        struct stat stbuf;
        char config_path[MAX_PATH_LEN];

        snprintf(__path__, MAX_PATH_LEN, "%s/etc/hosts.conf", gloconf.home);
        snprintf(config_path, MAX_PATH_LEN, "%s/lich.conf", LICH_CONFIG_PATH);

        ret = sy_spin_init(&table_lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        table = hash_create_table(__cmp, __key, "hosts table");
        if (table == NULL) {
                ret = ENOMEM;
                GOTO(err_ret, ret);
        }

        ret = stat(__path__, &stbuf);
        if (unlikely(ret)) {
                ret = errno;
                if (ret == ENOENT) {
                        goto out;
                } else
                        GOTO(err_ret, ret);
        }

        ret = __hosts_load(&count);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (ng.daemon) {
                ret = fnotify_register(__path__, __hosts_fnotify, __hosts_fnotify_del, NULL);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

out:
        return 0;
err_ret:
        return ret;
}

static int __hosts_dump(void *_context, void *_ent)
{
        entry_t *ent = _ent;

        (void) _context;

        if (strcmp(ent->rack, "default") == 0)
                printf("%s:%s\n", ent->addr, ent->node);
        else if (strcmp(ent->site, "default") == 0)
                printf("%s:%s.%s\n", ent->addr, ent->rack, ent->node);
        else
                printf("%s:%s.%s.%s\n", ent->addr, ent->site, ent->rack, ent->node);

        return 0;
}

void hosts_dump()
{
        hash_iterate_table_entries(table, __hosts_dump,  NULL);
}

int ip2hostname(const char *ip, char *name)
{
        FILE * fp = NULL;
        char buf[MAX_LINE_LEN] = "";
        int len = 0, find = 0;
        char _hostname[MAX_NAME_LEN] = "", _ip[MAX_NAME_LEN] = "", filename[MAX_NAME_LEN] = "";

        if (gloconf.nohosts) {
                sprintf(filename, "%s/etc/hosts.conf", gloconf.home);
        } else {
                sprintf(filename, "%s", "/etc/hosts");
        }

        fp = fopen(filename, "r");
        if (fp == NULL) {
                return -1;
        }

        while(fgets(buf, MAX_LINE_LEN, fp) != NULL) {
                len = strlen(buf);
                buf[len - 1] = '\0';

                sscanf(buf, "%s %s", _ip, _hostname);
                if (strcmp(_ip, ip) == 0) {
                        strcpy(name, _hostname);
                        find = 1;
                        break;
                }
        }

        fclose(fp);
        return find;
}


